import { IAudioConstraints, IPermissionName, IStream, IVideoConstraints } from './interface';
import EventEmit from './Event';
import { log } from './aop/log';
import MediaStreamConfigure from './config/MediaStreamConfigure';

class MediaDevice {
	public audioInput: MediaDeviceInfo[];
	public audioOutput: MediaDeviceInfo[];
	public videoInput: MediaDeviceInfo[];
	public videoOutPut: MediaDeviceInfo[];

	public videoInputDeviceId: string | undefined;
	public videoOutputDeviceId: string | undefined;
	public audioInputDeviceId: string | undefined;
	public audioOutputDeviceId: string | undefined;

	public audioStreamConstraints: IAudioConstraints;
	public videoStreamConstraints: IVideoConstraints;
	public allMediaDevicesMap: Map<string, MediaDeviceInfo>;
	private validMediaDevicesMap: Map<string, MediaDeviceInfo>;
	public communicationsMap: Map<string, MediaDeviceInfo>;

	public localStream: MediaStream | null;

	public publishOptions: any;

	constructor() {
		this.videoInputDeviceId = undefined;
		this.videoOutputDeviceId = '';
		this.audioInputDeviceId = undefined;
		this.audioOutputDeviceId = undefined;

		this.audioInput = [];
		this.audioOutput = [];
		this.videoInput = [];
		this.videoOutPut = [];
		this.communicationsMap = new Map();
		this.validMediaDevicesMap = new Map();
		this.allMediaDevicesMap = new Map();

		this.audioStreamConstraints = {
			deviceId: this.audioInputDeviceId,
			source: 'mic'
		};
		this.videoStreamConstraints = {
			deviceId: this.videoInputDeviceId,
			frameRate: 15,
			resolution: {
				width: 640,
				height: 480
			},
			source: 'camera'
		};
		this.publishOptions = MediaStreamConfigure.defaultPublishOptions;
		this.localStream = null;
	}

	public setAudioStreamConstraints(constraints: IAudioConstraints) {
		this.audioStreamConstraints = { ...this.audioStreamConstraints, ...constraints };
	}

	public setVideoStreamConstraints(constraints: IVideoConstraints) {
		this.videoStreamConstraints = { ...this.videoStreamConstraints, ...constraints };
	}

	@log('日志消息')
	public setAudioInputDeviceId(deviceId: string | undefined) {
		this.audioInputDeviceId = deviceId;
		this.setAudioStreamConstraints({ deviceId: this.audioInputDeviceId });
		return this.audioInputDeviceId;
	}
	@log('日志消息')
	public setAudioOutputDeviceId(deviceId: string | undefined) {
		this.audioOutputDeviceId = deviceId;
		return this.audioOutputDeviceId;
	}

	@log('日志消息')
	public setVideoInputDeviceId(deviceId: string | undefined) {
		this.videoInputDeviceId = deviceId;
		this.setVideoStreamConstraints({ deviceId: deviceId });
		return this.videoInputDeviceId;
	}

	@log('日志消息')
	public setVideoOutputDeviceId(deviceId: string | undefined) {
		this.videoOutputDeviceId = deviceId;
		return this.videoOutputDeviceId;
	}

	public async init() {
		try {
			const microphone = await this.vlidatePermissionWithPermissionName('microphone');
			const camera = await this.vlidatePermissionWithPermissionName('camera');
			// 已授权
			if (microphone.state === 'granted' || camera.state === 'granted') {
				await this.setAllMediaDevices();
			}
		} catch (error) {
			console.log('mediaDeviceEnum error:', error);
		}
	}

	public initEvent() {
		navigator.mediaDevices.ondevicechange = async event => {
			console.warn('ondevicechange:', event);
			const microphone = await this.vlidatePermissionWithPermissionName('microphone');
			const camera = await this.vlidatePermissionWithPermissionName('camera');
			// 已授权
			if (microphone.state === 'granted' || camera.state === 'granted') {
				await this.setAllMediaDevices();
				this.notifyDeviceChange();
			}
		};
	}

	public setAudioOutput(devices: MediaDeviceInfo[]) {
		this.audioOutput = [...devices];
	}

	public setAudioInput(devices: MediaDeviceInfo[]) {
		this.audioInput = [...devices];
	}

	public setVideoInput(devices: MediaDeviceInfo[]) {
		this.videoInput = [...devices];
	}

	public notifyDeviceChange() {
		if (this.allMediaDevicesMap.size === this.validMediaDevicesMap.size) return;
		if (this.allMediaDevicesMap.size > this.validMediaDevicesMap.size) {
			this.allMediaDevicesMap.forEach((value, key) => {
				if (!this.validMediaDevicesMap.has(key)) {
					this.allMediaDevicesMap.delete(key);
					// Message.normal(`设备 ${value.label} 已拔出`);
					if (this.audioInputDeviceId === value.deviceId && value.kind === 'audioinput') {
						this.setAudioInputDeviceId(undefined);
					}
					if (this.videoInputDeviceId === value.deviceId && value.kind === 'videoinput') {
						this.setVideoInputDeviceId(undefined);
					}
					if (this.audioOutputDeviceId === value.deviceId && value.kind === 'audiooutput') {
						this.setAudioOutputDeviceId(undefined);
					}
					EventEmit.setDeviceEventLinstener({ event: value.kind, type: 'remove' }, value);
				}
			});
		} else {
			this.validMediaDevicesMap.forEach((value, key) => {
				if (!this.allMediaDevicesMap.has(key)) {
					this.allMediaDevicesMap.set(key, value);
					// Message.normal(`设备 ${value.label} 已插入`);
					if (this.audioInputDeviceId === value.deviceId && value.kind === 'audioinput') {
						this.setAudioInputDeviceId(value.deviceId);
					}
					if (this.videoInputDeviceId === value.deviceId && value.kind === 'videoinput') {
						this.setVideoInputDeviceId(value.deviceId);
					}
					if (this.audioOutputDeviceId === value.deviceId && value.kind === 'audiooutput') {
						this.setAudioOutputDeviceId(value.deviceId);
					}
					EventEmit.setDeviceEventLinstener({ event: value.kind, type: 'add' }, value);
				}
			});
		}
	}

	public setDefaultAudioDeviceID() {
		let audioInputDevice = this.audioInput.find(item => this.communicationsMap.has(item.kind) && this.communicationsMap.get(item.kind)!.groupId === item.groupId);
		let audioOutputDevice = this.audioOutput.find(item => this.communicationsMap.has(item.kind) && this.communicationsMap.get(item.kind)!.groupId === item.groupId);
		// let videoInputDevice = this.videoInput[0]?.deviceId || undefined;
		if (audioInputDevice) {
			this.setAudioInputDeviceId(audioInputDevice.deviceId ? audioInputDevice.deviceId : undefined);
		} else {
			this.setAudioInputDeviceId(undefined);
		}
		if (audioOutputDevice) {
			this.setAudioOutputDeviceId(audioOutputDevice.deviceId);
		} else {
			let audioOutputDevice = this.audioOutput.find(item => this.audioInput.map(k => k.groupId === item.groupId));
			this.setAudioOutputDeviceId(audioOutputDevice ? audioOutputDevice.deviceId : undefined);
		}
		// this.setVideoInputDeviceId(videoInputDevice);
	}

	public async mediaDeviceEnum() {
		let devices: MediaDeviceInfo[] = [];
		try {
			const microphone = await this.vlidatePermissionWithPermissionName('microphone');
			const camera = await this.vlidatePermissionWithPermissionName('camera');
			if (microphone.state !== 'granted' || camera.state !== 'granted') {
				console.error('Permission reject');
			}

			if (!navigator.mediaDevices?.enumerateDevices) {
				console.error('enumerateDevices() not supported.');
			}
			devices = await navigator.mediaDevices.enumerateDevices();
		} catch (error: any) {
			console.error(`${error.name}: ${error.message}`);
		} finally {
			return devices;
		}
	}

	public createMediaStream() {
		return new Promise((resolve, reject) => {
			ivcs.agent.createStreamWithMediaTrackConstraints(
				{ audioConstraints: this.audioStreamConstraints, videoConstraints: this.videoStreamConstraints, publishOptions: this.publishOptions },
				(stream: any) => {
					this.localStream = stream.mediaStream;
					resolve(this.localStream);
				},
				(error: any) => {
					console.log('createStreamWithMediaTrackConstraints error:', error);
					reject(error);
				}
			);
		});
	}

	public replaceVideoStream(videoId: string) {
		return new Promise((resolve, reject) => {
			try {
				let video = document.getElementById(videoId) as HTMLVideoElement;
				if (video) {
					video.srcObject = this.localStream;
					video.play();
					resolve('ok');
				}
			} catch (error) {
				console.log('replaceVideoStream error:', error);
				reject(error);
			}
		});
	}

	public async updateStreamWithDeviceID(
		videoId: string,
		deviceId: string,
		oSuccess: (data: { code: number; message: string; data: { device: MediaDeviceInfo; stream: MediaStream; status: string } }) => void,
		onError: (data: { code: number; message: string; data: any }) => void
	) {
		if (!this.validateDeviceID(deviceId)) {
			onError({ code: 400, message: `${deviceId} is invalid`, data: null });
		}
		try {
			const device = this.findDeviceWithDevicID(deviceId);
			const stream = (await this.createMediaStream()) as MediaStream;
			const status = (await this.replaceVideoStream(videoId)) as string;
			console.log(device, stream, status);

			if (device && stream && status) {
				oSuccess({ code: 200, message: 'success', data: { device, stream, status } });
			} else {
				onError({ code: 400, message: `updateStreamWithDeviceID error`, data: null });
			}
		} catch (error) {
			onError({ code: 400, message: `updateStreamWithDeviceID error`, data: error });
		}
	}

	public switchSpeaker(videoId: string) {
		return new Promise((resolve, reject) => {
			let id = videoId || '';
			let video = document.getElementById(id) as HTMLMediaElement;
			if (video) {
				video
					.setSinkId(this.audioOutputDeviceId)
					.then(() => {
						console.log(`video is being played on ${video.sinkId}`);
						resolve(`video is being played on ${video.sinkId}`);
					})
					.catch((e: any) => reject(e));
			}
		});
	}

	public async vlidatePermissionWithPermissionName(permissionName: IPermissionName) {
		// 'microphone', 'camera'
		const { state } = await navigator.permissions.query({
			name: permissionName as PermissionName
		});
		return { state, name: permissionName };
	}

	public async getUserMediaPermission() {
		let constraints = {
			audio: true,
			video: true
		};
		try {
			const stream = await navigator.mediaDevices.getUserMedia(constraints);
			if (stream) {
				let tracks = stream.getVideoTracks();
				for (let track of tracks) {
					track.stop();
				}
				console.log('getUserMediaPermission success');
			}
		} catch (err) {
			console.log('getUserMediaPermission error', err);
		}
	}

	public async setInputDefaultDeviceID(stream: MediaStream) {
		const audioTrack = stream.getAudioTracks();
		const videoTrack = stream.getVideoTracks();
		for (let track of audioTrack) {
			if (track.getSettings().deviceId && this.validateDeviceID(track.getSettings().deviceId!)) {
				this.setAudioInputDeviceId(track.getSettings().deviceId);
			}
		}
		for (let track of videoTrack) {
			if (track.getSettings().deviceId && this.validateDeviceID(track.getSettings().deviceId!)) {
				this.setVideoInputDeviceId(track.getSettings().deviceId);
			}
		}

		if (this.validMediaDevicesMap.size === 0) {
			await this.setAllMediaDevices();
		}
	}

	public async setAllMediaDevices() {
		this.validMediaDevicesMap.clear();
		const devices = await this.mediaDeviceEnum();
		if (this.allMediaDevicesMap.size === 0) {
			devices.map(device => this.allMediaDevicesMap.set(`${device.kind}-${device.deviceId}`, device));
		}
		devices.map(device => {
			console.log(`${device.kind}-${device.deviceId}`);

			this.validMediaDevicesMap.set(`${device.kind}-${device.deviceId}`, device);
		});
		console.log(devices, this.allMediaDevicesMap.size, this.validMediaDevicesMap.size);
		this.updateDevices();
	}

	public findDeviceWithDevicID(deviceId: string) {
		return [...this.validMediaDevicesMap.values()].find(item => item.deviceId === deviceId);
	}

	public validateDeviceID(deviceId: string) {
		if ([...this.validMediaDevicesMap.values()].find(item => item.deviceId === deviceId)) {
			return true;
		}
		return false;
	}

	private updateDevices() {
		const audioInputDevices: MediaDeviceInfo[] = [];
		const audioOutputDevices: MediaDeviceInfo[] = [];
		const videoInputDevices: MediaDeviceInfo[] = [];
		this.validMediaDevicesMap.forEach((value, key) => {
			if (value.deviceId !== 'communications' && value.deviceId !== 'default') {
				if (value.kind === 'audioinput') {
					audioInputDevices.push(value);
				} else if (value.kind === 'audiooutput') {
					audioOutputDevices.push(value);
				} else if (value.kind === 'videoinput') {
					videoInputDevices.push(value);
				}
			} else {
				if (value.deviceId === 'default' && (value.kind === 'audiooutput' || value.kind === 'audioinput')) {
					this.communicationsMap.set(value.kind, value);
				}
			}
		});
		this.setAudioInput(audioInputDevices);
		this.setAudioOutput(audioOutputDevices);
		this.setVideoInput(videoInputDevices);
		this.setDefaultAudioDeviceID();
	}

	public async validateDevicesSupport() {
		await this.getUserMediaPermission();
		const devicees = await this.mediaDeviceEnum();
		console.log(devicees);
		let audio = devicees.find(item => item.kind === 'audioinput');
		let video = devicees.find(item => item.kind === 'videoinput');
		return { audio, video };
	}
}

export default MediaDevice;
